﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Infrastructure.Interface;
using System.IO;
using System.Web;
using System.Configuration;
using System.Xml;
using System.Reflection;
using System.Web.Configuration;
using System.Collections.Specialized;

namespace Infrastructure.Configuration
{
    public abstract class ConfigurationManagerBase : IConfigurationManager
    {
        private Dictionary<string, IConfigurationSectionHandler> configurationSectionhandlers = new Dictionary<string, IConfigurationSectionHandler>();
        private Dictionary<Type, MethodInfo> configurationSectionMethods = new Dictionary<Type, MethodInfo>();


        private System.Configuration.Configuration configuration = null;
        private bool isWeb = false;

        protected ConfigurationManagerBase(string configFile)
        {
            this.isWeb = (HttpContext.Current != null);

            //Since we are now running in the CAB we can't just use the "global" config file
            if (!this.isWeb && !string.IsNullOrEmpty(configFile))
            {
                if (!Path.IsPathRooted(configFile))
                {
                    //always translate into full paths - it makes it easier to sort out what went wrong if we get an error saying it can't be found
                    configFile = Path.GetFullPath(configFile);
                }

                if (!File.Exists(configFile))
                {
                    throw new ApplicationException("Unable to locate the configuration support file '" + configFile + "' for the configuration file '" + configFile + ".config'");
                }
                this.configuration = ConfigurationManager.OpenExeConfiguration(configFile);
                if (!this.configuration.HasFile)
                {
                    throw new ApplicationException("Unable to locate configuration file '" + configFile + ".config'");
                }
            }
        }


        public object GetSection(string sectionName)
        {
            if (this.configuration != null)
            {
                ConfigurationSection section = this.configuration.GetSection(sectionName);
                if (section == null) return null;
                if (section is DefaultSection)
                {
                    //if it's a default section then it's going to be an implementation of the old .net 1.1 interface....
                    XmlDocument document = new XmlDocument();
                    XmlElement element = document.CreateElement("root");
                    element.InnerXml = section.SectionInformation.GetRawXml();

                    IConfigurationSectionHandler handler = null;
                    lock (this.configurationSectionhandlers)
                    {
                        if (!this.configurationSectionhandlers.TryGetValue(section.SectionInformation.Type, out handler))
                        {
                            string type = section.SectionInformation.Type;

                            //Need to take the type information and convert it into a type object.
                            //When .Net does this itself there are some handlers which are in the 
                            //same assembly as the configuration handling code.  This means that they do 
                            //not need to be fully referenced (classname, assembly, version, culture, publickey).
                            //Because our code is not in that same assembly, a reference to one of these handlers that
                            //only includes the classname will fail where as in MS's code it would be fine.
                            //To simulate the behaviour expected, the handlers involved have been explicitly dealt with below.
                            Type typeToActivate;
                            switch (type)
                            {
                                case "System.Configuration.DictionarySectionHandler":
                                    typeToActivate = typeof(System.Configuration.DictionarySectionHandler);
                                    break;
                                case "System.Configuration.IgnoreSectionHandler":
                                    typeToActivate = typeof(System.Configuration.IgnoreSectionHandler);
                                    break;
                                case "System.Configuration.NameValueFileSectionHandler":
                                    typeToActivate = typeof(System.Configuration.NameValueFileSectionHandler);
                                    break;
                                case "System.Configuration.NameValueSectionHandler":
                                    typeToActivate = typeof(System.Configuration.NameValueSectionHandler);
                                    break;
                                case "System.Configuration.SingleTagSectionHandler":
                                    typeToActivate = typeof(System.Configuration.SingleTagSectionHandler);
                                    break;
                                default:
                                    typeToActivate = Type.GetType(type);
                                    if (typeToActivate == null) throw new ApplicationException("Unable to find Section Handler specified with type '" + type + "'.  The specified type should (at a minimum) specify the full class name (namespace + class name) as well as the assembly (e.g. \"System.Configuration.NameValueSectionHandler, System.Configuration\").  Note that if the assembly that contains the section handler is in the Global Assembly Cache then the full information must be supplied (assembly, version, culture and public key)");
                                    break;
                            }

                            object activatedObject = Activator.CreateInstance(typeToActivate);

                            handler = activatedObject as IConfigurationSectionHandler;
                            if (handler != null) this.configurationSectionhandlers.Add(type, handler);
                        }
                    }

                    if (handler != null)
                    {
                        return handler.Create(null, null, element.FirstChild);
                    }
                }

                //It's not a IConfigurationSectionHandler based section so it has to be COnfigurationSection base class based!

                //ok - don't really want to do reflection here - kinda sucks but I need to fetch it becuase this method is the only 
                //one that exposes the section object that is the same one exposed by ConfigurationManager.GetSection.
                //It's actually a protected virtual method that is intended to be overridden by implementation classes of 
                //ConfigurationSection.  As such it is extremely unlikely to change - ever.
                MethodInfo method;
                lock (configurationSectionMethods)
                {
                    if (!this.configurationSectionMethods.TryGetValue(section.GetType(), out method))
                    {
                        method = section.GetType().GetMethod("GetRuntimeObject", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                        this.configurationSectionMethods.Add(section.GetType(), method);
                    }
                }
                return method.Invoke(section, null);
            }
            else if (this.isWeb)
            {
                return WebConfigurationManager.GetSection(sectionName);
            }
            else
            {
                return ConfigurationManager.GetSection(sectionName);
            }
        }

        public string GetAppSetting(string key)
        {
            return GetAppSetting(key, string.Empty);
        }

        public string GetAppSetting(string key, string defaultValue)
        {

            string setting = this.AppSettings[key];

            if (setting == null) setting = defaultValue;
            return setting;
        }

        public NameValueCollection AppSettings
        {
            get
            {
                if (this.configuration != null)
                {
                    return (NameValueCollection)this.GetSection("appSettings");
                }
                else if (this.isWeb)
                {
                    return WebConfigurationManager.AppSettings;
                }
                else
                {
                    return ConfigurationManager.AppSettings;
                }
            }
        }

        public void WriteAppSetting(string key, string newValue)
        {
            this.configuration.AppSettings.Settings.Remove(key);
            this.configuration.AppSettings.Settings.Add(key, newValue);
            this.configuration.Save(ConfigurationSaveMode.Modified);
            ConfigurationManager.RefreshSection("appSettings");
        }

    }
}
